package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.FieldIR;
import mirrormonkey.util.annotations.parsing.MemberIR;
import mirrormonkey.util.annotations.parsing.MethodIR;
import mirrormonkey.util.annotations.parsing.memberlevel.FlatMemberIRManagementTest.LegalMemberIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning the instantiation and management of member IRs
 * in class hierarchies. This includes overriding IRs with others when
 * traversing down the hierarchy.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class HierarchicalMemberIRManagementTest {

	public static AnnotationParser parser;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		parser = new AnnotationParser();
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
		public String value();
	}

	public static class DeclareSingleField {
		@IRClass(FieldIR.class)
		public Object field;
	}

	public static class ExtendDeclareSingleFieldAndOverrideIR extends
			DeclareSingleField {
		@IRClass(LegalMemberIR.class)
		public Object field;
	}

	/**
	 * Tests if field IRs are recognized and replaced correctly in hierarchies.
	 */
	@Test
	public void testFieldIROverride() {
		ClassIR ir = parser.parseClass(
				ExtendDeclareSingleFieldAndOverrideIR.class, null);
		Assert.assertEquals(1, ir.memberIRs.get(Field.class).size());
		Assert.assertEquals(LegalMemberIR.class, ir.memberIRs.get(Field.class)
				.iterator().next().getClass());
	}

	public static class ExtendDeclareSingleFieldAndDeclareAnotherField extends
			DeclareSingleField {
		@IRClass(LegalMemberIR.class)
		public Object field2;
	}

	/**
	 * Tests if fields are not overridden if the name doesn't match.
	 */
	@Test
	public void testFieldIRNameReject() {
		ClassIR ir = parser.parseClass(
				ExtendDeclareSingleFieldAndDeclareAnotherField.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Field.class).size());
	}

	public static class DeclareStringField {
		@IRClass(FieldIR.class)
		public String field;
	}

	public static class DeclareObjectField extends DeclareStringField {
		@IRClass(FieldIR.class)
		public Object field;
	}

	/**
	 * Tests if trying to override a field with same name but different type
	 * will provoke an exception.
	 */
	@Test
	public void testFieldIRTypeReject() {
		ClassIR ir = parser.parseClass(DeclareObjectField.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Field.class).size());
	}

	public static class DeclareStringSubfield extends DeclareSingleField {
		@IRClass(FieldIR.class)
		public String field;
	}

	/**
	 * Tests if trying to override a field with same name but different type
	 * will provoke an exception even if the overriding type can be cast to the
	 * overridden type.
	 */
	@Test
	public void testFieldIRSubtypeReject() {
		ClassIR ir = parser.parseClass(DeclareStringSubfield.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Field.class).size());
	}

	public static class DeclareSingleMethod {
		@IRClass(MethodIR.class)
		public Object method() {
			return null;
		}
	}

	public static class OverrideSingleMethod extends DeclareSingleMethod {
		@Override
		@IRClass(LegalMemberIR.class)
		public Object method() {
			return null;
		}
	}

	/**
	 * Tests if method IRS are recognized and replaced correctly in hierarchies.
	 */
	@Test
	public void testMethodIROverride() {
		ClassIR ir = parser.parseClass(OverrideSingleMethod.class, null);
		Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
		Assert.assertEquals(LegalMemberIR.class, ir.memberIRs.get(Method.class)
				.iterator().next().getClass());
	}

	public static class DeclareAnotherMethod extends DeclareSingleMethod {
		@IRClass(MethodIR.class)
		public Object method2() {
			return null;
		}
	}

	/**
	 * Tests if another method IR is created for method names that do not exist
	 * yet.
	 */
	@Test
	public void testMethodIRNameReject() {
		ClassIR ir = parser.parseClass(DeclareAnotherMethod.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public static class DeclareCustomSubtype extends DeclareSingleMethod {
		@Override
		@IRClass(MethodIR.class)
		public String method() {
			return null;
		}
	}

	/**
	 * Tests if another method IR is created if the name matches and the return
	 * type can be cast to to the return type of the (not) overridden method.
	 */
	@Test
	public void testMethodIRSubtypeReject() {
		ClassIR ir = parser.parseClass(DeclareCustomSubtype.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public static class DeclareVoidMethod {
		@IRClass(MethodIR.class)
		public void method() {
		}
	}

	public static class ExtendVoidMethod {
		@IRClass(LegalMemberIR.class)
		public void method() {
		}
	}

	/**
	 * Tests if methods returning void are handled just as correctly as ones
	 * that don't.
	 */
	@Test
	public void testVoidMethodOverride() {
		ClassIR ir = parser.parseClass(ExtendVoidMethod.class, null);
		Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
		Assert.assertEquals(LegalMemberIR.class, ir.memberIRs.get(Method.class)
				.iterator().next().getClass());
	}

	public static class DeclareAnotherVoidMethod extends DeclareVoidMethod {
		@IRClass(MethodIR.class)
		public void method(Object param) {
		}
	}

	/**
	 * Tests if another method IR is created if the name and return type match
	 * but the methods don't have equal parameter count.
	 */
	@Test
	public void testMethodIRParamCountReject() {
		ClassIR ir = parser.parseClass(DeclareAnotherVoidMethod.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public static class DeclareMethodWithStringParam {
		@IRClass(MethodIR.class)
		public void method(String arg) {
		}
	}

	public static class ExtendStringParam extends DeclareMethodWithStringParam {
		@IRClass(MethodIR.class)
		public void method(Object arg) {
		}
	}

	/**
	 * Tests if another method IR is created if the name and return type match
	 * but the method parameters do not.
	 */
	@Test
	public void testMethodIRParamTypeReject() {
		ClassIR ir = parser.parseClass(ExtendStringParam.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public static class DeclareMethodWithObjectParam {
		@IRClass(MethodIR.class)
		public void method(Object arg) {
		}
	}

	public static class ExtendMethodWithObjectParam extends
			DeclareMethodWithObjectParam {
		@IRClass(MethodIR.class)
		public void method(String arg) {
		}
	}

	/**
	 * Tests if another method IR is created if the name, return type and all
	 * but one parameter types match but one of the method parameters do not
	 * match exactly.
	 */
	@Test
	public void testMethodIRParamSubtypeReject() {
		ClassIR ir = parser.parseClass(ExtendMethodWithObjectParam.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public class AnnotationOverrideSingleField extends DeclareSingleField {
		@AnnotationOverride
		public Object field;
	}

	/**
	 * Tests if field IRs are removed if an <tt>AnnotationOverride</tt> is
	 * encountered when traversing the class hierarchy.
	 */
	@Test
	public void testFieldIRRemove() {
		ClassIR ir = parser.parseClass(AnnotationOverrideSingleField.class,
				null);
		for (Class<?> i : new Class<?>[] { Constructor.class, Method.class,
				Field.class }) {
			Assert.assertTrue(ir.memberIRs.get(i).isEmpty());
		}
	}

	public class AnnotationOverrideAndRecreateSingleField extends
			DeclareSingleField {
		@IRClass(FieldIR.class)
		@AnnotationOverride
		public Object field;
	}

	/**
	 * Tests if field IRs are created if <tt>AnnotationOverride</tt> and
	 * <tt>IRClass</tt> are encountered on the same field in the same level of
	 * the hierarchy.
	 */
	@Test
	public void testFieldIRRecreate() {
		ClassIR ir = parser.parseClass(
				AnnotationOverrideAndRecreateSingleField.class, null);
		Assert.assertEquals(1, ir.memberIRs.get(Field.class).size());
	}

	public static class ExtendAndOverrideSingleMethod extends
			DeclareSingleMethod {
		@Override
		@AnnotationOverride
		public Object method() {
			return null;
		}
	}

	/**
	 * Tests if method IRs are removed if an <tt>AnnotationOverride</tt> is
	 * encountered when traversing the class hierarchy.
	 */
	@Test
	public void testMethodIRRemove() {
		ClassIR ir = parser.parseClass(ExtendAndOverrideSingleMethod.class,
				null);
		for (Class<?> i : new Class<?>[] { Constructor.class, Method.class,
				Field.class }) {
			Assert.assertTrue(ir.memberIRs.get(i).isEmpty());
		}
	}

	public static class ExtendAndRecreateSingleMethod {
		@IRClass(MethodIR.class)
		@AnnotationOverride
		public Object method() {
			return null;
		}
	}

	/**
	 * Tests if new method IRs are created if <tt>AnnotationOverride</tt> and
	 * <tt>IRClass</tt> are encountered on the same method in the same level of
	 * the hierarchy.
	 */
	@Test
	public void testMethodIRRecreate() {
		ClassIR ir = parser.parseClass(ExtendAndRecreateSingleMethod.class,
				null);
		Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
	}

	public static class InvalidFollowupIR extends MemberIR {

		public InvalidFollowupIR(InvalidFollowupIR previous, Member member) {
			super(previous, (AnnotatedElement) member);
		}

		@Override
		public boolean matches(Member member) {
			return false;
		}

		@Override
		public void parseField(Field f) {
		}

		@Override
		public void parseMethod(Method m) {
		}

		@Override
		public void parseConstructor(Constructor<?> constr) {
		}

		@Override
		public Class<?>[] getListenMemberClasses() {
			return null;
		}

	}

	public class ExtendSingleFieldWithInvalidFollowup {
		@IRClass(InvalidFollowupIR.class)
		public Object field;
	}

	/**
	 * Tests if using an IR class that does not define a constructor for the one
	 * used previously does provoke an exception.
	 */
	@Test(expected = RuntimeException.class)
	public void testInvalidFollowupIRClass() {
		parser.parseClass(ExtendSingleFieldWithInvalidFollowup.class, null);
	}

}
